#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <string>
#include <functional>
#include "log.hpp"
using namespace LogModule;
using func_t =std::function<std::string(const std::string&)>;
std::string defaulthandler(const std::string& mess)
{
    return "hello "+mess;
}
class udpserver
{
private:
    int _sockfd;
    uint16_t _port;
    std::string _ip;    
    bool _isrunning;
    func_t _func;

public:
    udpserver(uint16_t port/*,std::string ip*/,func_t func=defaulthandler) : _sockfd(-1),_port(port),/*_ip(ip),*/_isrunning(false),_func(func)
    {
    }
    void init()
    {
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 如果创建成功返回文件描述符,创建套接字只是打开了网络文件
        if (_sockfd < 0)
        {
            LOG(Loglevel::FATAL)<<"socket error"<<"\r\n";
            exit(1);
        }
        LOG(Loglevel::INFO)<<"socket success,sockfd:"<<_sockfd<<"\r\n";
        struct sockaddr_in local;
        bzero(&local,sizeof(local));
        local.sin_addr.s_addr=INADDR_ANY;  
        local.sin_family=AF_INET;
        local.sin_port=htons(_port); //server不建议手动绑定特定的IP,INADDR_ANY允许该服务器上所有绑定的IP访问
        
        int n=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));//bind()：才会给这个 socket 绑定本地 IP 和端口。比如 bind(192.168.1.10:8888)，意思是以后发出去的数据包都从这个本地 IP/端口发。
        //为什么服务器需要显示的bond呢？因为服务器的IP和端口一定是总所周知且不能随意改变的！一旦改变了，那用户端就访问不到了。
        

        if(n!=0){
            LOG(Loglevel::FATAL)<<"bind error";
            exit(2);
        }
        LOG(Loglevel::INFO)<<"bind success";


    }
    void Start()
    {
        _isrunning=true;
        while(true)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len=sizeof(peer);
            ssize_t s=recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);//注意peer是一个输出型参数，len是一个输入输出型参数。未来有一个进程给服务器发消息就自动将该进程的IP和port记录到了peer中。
            if(s>0)
            {
                int peer_port=ntohs(peer.sin_port);
                std::string peer_ip=inet_ntoa(peer.sin_addr);
                buffer[s]='\0';
                LOG(Loglevel::DEBUG)<<"[client_ip:"<<peer_ip<<"  client_port:"<<peer_port<<"]  #"<<"buffer:"<<buffer;
                std::string echo_string="server echo@";
                echo_string+=buffer;
                sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&peer,len);//注意这里的peer就是一个输入型参数，该参数还有要发给线程的IP和port，_sockfd实际是一个文件描述符表,其跟一个固定的IP和PORTbind了。
            }

        }
    }
    ~udpserver()
    {
    }
};
// socket + bind：就像你注册了一个手机号（本地 IP+端口），大家知道你是谁。
// dest_addr：就像你拨打的对方号码。


// 对 UDP 来说：
// socket 只知道“从哪里发”（本地 IP/端口）。
// 但每一条消息可能要发往不同的目标 → 所以 sendto 必须传 dest_addr。
// 你也可以用 connect() 把 UDP socket 固定到某个目标，这样以后就不用传 dest_addr 了，直接 send() 即可。
// 对 TCP 来说：
// 你在 connect() 时已经确定了目标地址。
// 内核把这个目标地址记录在 socket 对象里。
// 所以后面调用 send()/sendto() 时，不需要再传 dest_addr（传了也会被忽略）。